package de.tu_darmstadt.gdi1.xcom.controller;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;

import de.tu_darmstadt.gdi1.framework.controller.AbstractController;
import de.tu_darmstadt.gdi1.framework.events.DefaultUserInterfaceEvent;
import de.tu_darmstadt.gdi1.framework.exceptions.InvalidLevelDataException;
import de.tu_darmstadt.gdi1.framework.exceptions.NoNextLevelException;
import de.tu_darmstadt.gdi1.framework.exceptions.SoundFailedException;
import de.tu_darmstadt.gdi1.framework.interfaces.IController;
import de.tu_darmstadt.gdi1.framework.interfaces.IControllerEvent;
import de.tu_darmstadt.gdi1.framework.interfaces.IUserInterfaceEvent;
import de.tu_darmstadt.gdi1.framework.utils.Point;
import de.tu_darmstadt.gdi1.framework.utils.level.Level;
import de.tu_darmstadt.gdi1.framework.utils.level.LevelManager;
import de.tu_darmstadt.gdi1.xcom.controller.ai.XcomAIInterface;
import de.tu_darmstadt.gdi1.xcom.controller.ai.XcomMediumAI;
import de.tu_darmstadt.gdi1.xcom.controller.ai.XcomSimpleAI;
import de.tu_darmstadt.gdi1.xcom.controller.events.ActionEnum;
import de.tu_darmstadt.gdi1.xcom.controller.events.ApplicationEventEnum;
import de.tu_darmstadt.gdi1.xcom.controller.events.GameStateEnum;
import de.tu_darmstadt.gdi1.xcom.controller.events.UserActionEnum;
import de.tu_darmstadt.gdi1.xcom.controller.events.XcomActionEvent;
import de.tu_darmstadt.gdi1.xcom.controller.events.XcomApplicationEvent;
import de.tu_darmstadt.gdi1.xcom.controller.events.XcomGameStateEvent;
import de.tu_darmstadt.gdi1.xcom.controller.events.XcomUserActionEvent;
import de.tu_darmstadt.gdi1.xcom.controller.level.XcomLevelInformationProvider;
import de.tu_darmstadt.gdi1.xcom.controller.level.XcomLevelManager;
import de.tu_darmstadt.gdi1.xcom.model.XcomGameData;
import de.tu_darmstadt.gdi1.xcom.model.XcomTeam;
import de.tu_darmstadt.gdi1.xcom.model.elements.Ammo;
import de.tu_darmstadt.gdi1.xcom.model.elements.DirectionEnum;
import de.tu_darmstadt.gdi1.xcom.model.elements.Door;
import de.tu_darmstadt.gdi1.xcom.model.elements.GameElement;
import de.tu_darmstadt.gdi1.xcom.model.elements.Human;
import de.tu_darmstadt.gdi1.xcom.model.elements.Medikit;
import de.tu_darmstadt.gdi1.xcom.model.elements.Mob;
import de.tu_darmstadt.gdi1.xcom.model.elements.Switch;
import de.tu_darmstadt.gdi1.xcom.view.XcomUI;
import de.tu_darmstadt.gdi1.xcom.view.events.UIStateChangeEvent;

public class XcomController extends AbstractController implements IController {

	private LevelManager<GameElement> levelManager;
	private XcomUI ui;
	private XcomGameData gd;

	private Point attackedField;

	private boolean mapVisible = false;

	public boolean loadedFromTest = false;

	public XcomController() {
		super();
		levelManager = new XcomLevelManager(new XcomLevelInformationProvider(false));
	}

	public void loadLevel(String level) {
		try {

			levelManager = new XcomLevelManager(
					new XcomLevelInformationProvider(true));
			gd = (XcomGameData) levelManager.loadLevel(level);
			if (gd != null) {
				gd.setTeam1IsActive(true);
				gd.getTeam(false).setAI(new XcomMediumAI());
				resetGame();
			}
		} catch (InvalidLevelDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public List<GameElement> getNeighborGameElements(Point coordinates,
			DirectionEnum direction) {
		Point neighborCoordinates = new Point(coordinates.getX()
				+ direction.getStep().getX(), coordinates.getY()
				+ direction.getStep().getY());
		if (gd.getStepManager().checkCoordinates(neighborCoordinates))
			return getGameElementsOnField(neighborCoordinates);
		return null;
	}

	public List<GameElement> getGameElementsOnField(Point coordinates) {
		return gd.getStepManager().getElements(coordinates);
	}

	private boolean directLineOfSight(Point a, Point b) {

		if (Math.abs(b.getY() - a.getY()) < Math.abs(b.getX() - a.getX())) {
			// do horizontal check
			Point left, right;
			if (a.getX() < b.getX()) {
				left = a;
				right = b;
			} else {
				left = b;
				right = a;
			}

			// linear function: f(x) = m*x + n
			float m = ((float) (right.getY() - left.getY()))
					/ ((float) (right.getX() - left.getX()));

			for (double x = left.getX(), y = left.getY(); x <= right.getX(); x += 1, y += m) {
				if (getGameElementsOnField(
						new Point((int) (x + 0.5), (int) (y + 0.5))).get(0)
						.isSolid())
					return false;
			}

			return true;

		} else {
			// do vertical check
			Point top, bottom;
			if (a.getY() < b.getY()) {
				top = a;
				bottom = b;
			} else {
				top = b;
				bottom = a;
			}

			// linear function: f(y) = m*y + n from top to bottom
			float m = ((float) (bottom.getX() - top.getX()))
					/ (float) (bottom.getY() - top.getY());

			for (double x = top.getX(), y = top.getY(); y <= bottom.getY(); y++, x += m) {
				if (getGameElementsOnField(
						new Point((int) (x + 0.5), (int) (y + 0.5))).get(0)
						.isSolid())
					return false;
			}

			return true;

		}

	}

	private boolean lineOfSight(Point a, Point b) {
		if (getGameElementsOnField(b).get(0).isSolid())
			return directLineOfSight(a,
					new Point(Math.max(0, b.getX() - 1), b.getY()))
					|| directLineOfSight(
							a,
							new Point(Math.min(
									gd.getStepManager().getWidth() - 1,
									b.getX() + 1), b.getY()))
					|| directLineOfSight(a,
							new Point(b.getX(), Math.max(0, b.getY() - 1)))
					|| directLineOfSight(
							a,
							new Point(b.getX(), Math.min(gd.getStepManager()
									.getHeight() - 1, b.getY() + 1)));
		else
			return directLineOfSight(a, b);
	}

	/**
	 * check in this method if a field is visible concerning the fog of war if a
	 * human player (/ player of the active team) is within a range of 4 fields,
	 * the field is visible.
	 */
	public boolean getVisibilityForField(Point coordinates) {
		if (mapVisible)
			return true;

		// get through the list of MOBs of the current active team and check if
		// any of them
		// is in the visibility range
		for (Mob activeMob : gd.getActiveTeamMobs()) {

			// relative coordinates. (transformed so that activeMob is at 0,0)
			int relX = coordinates.getX() - activeMob.getPosition().getX();
			int relY = coordinates.getY() - activeMob.getPosition().getY();

			// check if point is in circle around activeMob
			if (Math.sqrt(Math.pow(relX, 2) + Math.pow(relY, 2)) <= 20.5) // 4.5
																			// is
																			// the
																			// viewing
																			// distance
				// check if mob is looking at point
				switch (activeMob.getDirection()) {
				case East:
					// check if point lies within view frustrum
					if (activeMob.getPosition().getX() <= coordinates.getX()
							&& (Math.abs(relY) <= Math.abs(relX)))
						if (lineOfSight(activeMob.getPosition(), coordinates))
							return true;
					break;
				case North:
					if (activeMob.getPosition().getY() >= coordinates.getY()
							&& (Math.abs(relX) <= Math.abs(relY)))
						if (lineOfSight(activeMob.getPosition(), coordinates))
							return true;
					break;
				case South:
					if (activeMob.getPosition().getY() <= coordinates.getY()
							&& (Math.abs(relX) <= Math.abs(relY)))
						if (lineOfSight(activeMob.getPosition(), coordinates))
							return true;
					break;
				case West:
					if (activeMob.getPosition().getX() >= coordinates.getX()
							&& (Math.abs(relY) <= Math.abs(relX)))
						if (lineOfSight(activeMob.getPosition(), coordinates))
							return true;
					break;
				}

		}
		// no active player found within the range
		return false;
	}

	@Override
	public void processEvent(IControllerEvent event) {
		if (gd == null)
			return;

		if (event instanceof XcomGameStateEvent)
			handleStateChange((XcomGameStateEvent) event);
		if (event instanceof XcomUserActionEvent)
			handleUserAction((XcomUserActionEvent) event);
		if (event instanceof XcomActionEvent)
			handleActionEvent((XcomActionEvent) event);
		if (event instanceof XcomApplicationEvent)
			handleApplicationEvent((XcomApplicationEvent) event);

		if (gd.getGameState() == GameStateEnum.RUNNING)
			checkWon();
	}

	private void handleApplicationEvent(XcomApplicationEvent event) {
		if (event == null)
			return;
		ApplicationEventEnum appEv = event.getEvent();
		String file;

		switch (appEv) {

		case QUIT:
			this.stopWorker();
			return;

		case NEW:
			try {
				levelManager = new XcomLevelManager(
						new XcomLevelInformationProvider(false));
				gd = (XcomGameData) levelManager.loadNextLevel();
				gd.setLevel(levelManager.getCurrentLevelName());
				resetGame();
			} catch (InvalidLevelDataException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (NoNextLevelException e) {
				e.printStackTrace();
			}
			break;

		case LoadNextLevel:
			try {
				File path = levelManager.getPath();
				if(gd.getNextLevel() == null || gd.getNextLevel().length() < 1){
					this.processEvent(new XcomApplicationEvent(ApplicationEventEnum.NEW));
					return;
				}
				File level = new File(path.getAbsolutePath() + "\\"
						+ gd.getNextLevel());
				gd = (XcomGameData) levelManager.loadLevel(level);
				gd.setLevel(levelManager.getCurrentLevelName());
				resetGame();
			} catch (InvalidLevelDataException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			break;

		case RESTART:
			try {
				gd = (XcomGameData) levelManager.loadCurrentAgain();
				gd.setLevel(levelManager.getCurrentLevelName());
				resetGame();
			} catch (InvalidLevelDataException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			break;

		case LOAD:
			file = event.getString();
			try {
				gd = (XcomGameData) levelManager.loadLevel(new File(file));
				gd.setLevel(levelManager.getCurrentLevelName());
				resetGame();
			} catch (InvalidLevelDataException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			break;

		case SAVE:
			file = event.getString();
			try {
				levelManager.saveBoardAsLevel(new File(file), gd);
			} catch (InvalidLevelDataException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			break;

		case VISIBILITY:
			mapVisible = !mapVisible;
			break;

		default:
			return;

		}
		updateBoard();
	}

	private void handleUserAction(XcomUserActionEvent event) {
		if (gd.getGameState() != GameStateEnum.RUNNING)
			return;
		int key = 0;
		switch (event.getUserAction()) {
		case KeyPressed:
			// get the key-number
			key = event.getIntOne();
			// get the current MOB
			break;
		case FieldClicked:
			int x = event.getIntOne();
			int y = event.getIntTwo();
			key = getKeyFromMouse(x, y);
			break;
		case ChangeMOB:
			int index = event.getIntOne();
			Mob newMob = (Mob) gd
					.getStepManager()
					.getElements(
							gd.getActiveTeam().getMobPositions().get(index))
					.get(1);
			gd.getActiveTeam().setSelectedMob(index, newMob);
			updateBoard();
			return;
		case EndTurn:
			XcomActionEvent ev = new XcomActionEvent(ActionEnum.EndTurn);
			this.handleEvent(ev);
			// endTurn();
			return;
		}
		XcomActionEvent ev = new XcomActionEvent(getActionFromKey(key));
		if (loadedFromTest)
			this.processEvent(ev);
		else
			this.handleEvent(ev);
	}

	private boolean checkWon() {
		if (gd.isWon()) {
			if (loadedFromTest)
				processEvent(new XcomGameStateEvent(GameStateEnum.GAME_WON));
			else
				handleEvent(new XcomGameStateEvent(GameStateEnum.GAME_WON));
			return true;
		}
		if (gd.isLost()) {
			if (loadedFromTest)
				processEvent(new XcomGameStateEvent(GameStateEnum.GAME_LOST));
			else
				handleEvent(new XcomGameStateEvent(GameStateEnum.GAME_LOST));
			return true;
		}
		return false;
	}

	private ActionEnum getActionFromKey(int key) {
		switch (key) {
		case 32:
			return ActionEnum.Shoot;
		case 37:
		case 38:
		case 39:
		case 40:
			return gd.getActiveTeam().getCurrentMob()
					.getMoveAction(DirectionEnum.getDirectionFromKey(key));
		}
		return null;
	}

	public int getKeyFromMouse(int x, int y) {

		Point selectedPosition = new Point(x, y);
		// check if there's a player on this field which the user
		// wants to select
		int index = gd.getActiveTeam().getMobPositions()
				.indexOf(selectedPosition);
		if (index != -1) {
			XcomUserActionEvent ev = new XcomUserActionEvent(
					UserActionEnum.ChangeMOB);
			ev.setIntOne(index);
			if (loadedFromTest)
				this.processEvent(ev);
			else {
				this.handleEvent(ev);
				updateBoard();
			}
			return 0;
		}

		Mob currentMob = gd.getActiveTeam().getCurrentMob();

		// if both points are equal, do nothing (yet)
		if (currentMob.getPosition().equals(selectedPosition))
			return 0;
		// x-component is less different than y-component, so the user wants
		// "more"
		// to navigate to y (we dont use pathfinding here, the mouse-click can
		// be
		// interpreted as "vector" for a specific direction!)
		if (xComponentIsCloser(currentMob.getPosition(), new Point(x, y))) {
			// user wants to navigate on the y-axis, check which direction

			if (currentMob.getPosition().getY() < y) {
				// y-position of Mob is smaller than destination -
				// user wants to navigate to south, so check if it is
				// possible with his current direction
				return 40;
				// no handling if user is facing north
			} else {
				// y-position of Mob is greater than destination -
				// user wants to navigate to north, so check if it is
				// possible with his current direction
				return 38;
				// no handling if user is facing south
			}
		} else {
			// user wants to navigate on the x-axis, check which direction
			if (currentMob.getPosition().getX() < x) {
				// x-position of Mob is smaller than destination -
				// user wants to navigate to east, so check if it is
				// possible with his current direction
				return 39;
				// no handling if user is facing west
			} else {
				// x-position of Mob is greater than destination -
				// user wants to navigate to west, so check if it is
				// possible with his current direction
				return 37;
				// no handling if user is facing east
			}
		}
	}

	private void handleStateChange(XcomGameStateEvent event) {
		switch (event.getNewState()) {
		case GAME_WON:
			showWonWindow();
			gd.setGameState(GameStateEnum.FINISHED);
			break;
		case GAME_LOST:
			showLOSTWindow();
			gd.setGameState(GameStateEnum.FINISHED);
			break;
		case RUNNING:
			gd.setGameState(GameStateEnum.RUNNING);
			break;
		case PAUSED:
			if (gd.getGameState() != GameStateEnum.PAUSED) {
				gd.saveState();
				gd.setGameState(GameStateEnum.PAUSED);
				try {
					ui.handleEvent(new UIStateChangeEvent(gd.getGameState()));
				} catch (SoundFailedException e) {
					e.printStackTrace();
				}
			}
			break;
		case CONTINUE:
			if (gd.getGameState() == GameStateEnum.PAUSED) {
				gd.restoreState();
				try {
					ui.handleEvent(new UIStateChangeEvent(gd.getGameState()));
				} catch (SoundFailedException e) {
					e.printStackTrace();
				}
			}
			break;
		}
	}

	private void showLOSTWindow() {
		if (loadedFromTest)
			return;
		try {
			ui.handleEvent(new UIStateChangeEvent(GameStateEnum.GAME_LOST));
		} catch (SoundFailedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void showWonWindow() {
		if (loadedFromTest)
			return;
		try {
			ui.handleEvent(new UIStateChangeEvent(GameStateEnum.GAME_WON));
		} catch (SoundFailedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	protected void initialize() {

		if (!loadedFromTest) {
			try {
				ui = new XcomUI(this);
				this.handleApplicationEvent(new XcomApplicationEvent(
						ApplicationEventEnum.NEW));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			resetGame();

			updateBoard();
		}
	}

	/**
	 * check if the x-component of the destination-point is closer or the
	 * y-component (notice: if both are the same, then x is chosen)
	 */
	private boolean xComponentIsCloser(Point o, Point d) {
		return (Math.abs(o.getX() - d.getX()) < Math.abs(o.getY() - d.getY()));
	}

	private void handleActionEvent(XcomActionEvent ev) {

		if (gd.getGameState() != GameStateEnum.RUNNING
				&& gd.getGameState() != GameStateEnum.AIOnTurn)
			return;

		if (ev == null)
			return;
		ActionEnum ae = ev.getAction();
		if (ae == null || ae == ActionEnum.Idle)
			return;

		if (ev.getAction() == ActionEnum.EndTurn) {
			endTurn();
			return;
		}

		if (gd.getActiveTeam().getCurrentMob().getPoints() <= 0)
			return;

		switch (ae) {
		case Attack:
			performMobAction(true);
			break;
		case Move:
			performMobAction(false);
			break;
		case RotateLeft:
			rotateMob(true);
			break;
		case RotateRight:
			rotateMob(false);
			break;
		case Shoot:
			performShootAction();
			break;
		}

		updateBoard();

		if (!loadedFromTest)
			performEnemyReaction();
	}

	public void performAutoHumanShoot(int x, int y) {
		Point humanPos = new Point(x, y);
		Mob human = gd.getMobOnPosition(humanPos);
		if (human != null && (human instanceof Human)) {
			List<Point> enemyMobs = gd.getTeam(false).getMobPositions();
			int i = 0;
			while (i < enemyMobs.size()) {
				Point m = enemyMobs.get(i);
				if (canAttack((Human) human, gd.getMobOnPosition(m))) {
					if (((Human) human).getMunition() < 1
							|| human.getPoints() < 10)
						return;
					human.decrementPoints(10);
					((Human) human).decreaseMunition(1);
					shootField(human.getPosition(), human.getDirection()
							.getStep(), ((Human) human).getWeaponDamage());
					return;
				}
				i++;
			}
		}
	}

	private void performEnemyReaction() {
		Human human = getCanAttackHuman(gd.getEnemyTeam().getMobPositions(), gd
				.getActiveTeam().getCurrentMob());
		if (human != null) {
			human.decrementPoints(10);
			human.decreaseMunition(1);
			shootField(human.getPosition(), human.getDirection().getStep(),
					human.getWeaponDamage());
			performEnemyReaction();
		}
	}

	private Human getCanAttackHuman(List<Point> list, Mob mob) {
		if (mob.getHealth() < 1)
			return null;
		for (Point m : list) {
			Human human = null;
			if (gd.getMobOnPosition(m) instanceof Human)
				human = (Human) gd.getMobOnPosition(m);
			if (human != null && human.getPoints() >= 10
					&& human.getMunition() > 0 && canAttack(human, mob))
				return human;
		}
		return null;
	}

	private boolean canAttack(Human human, Mob mob) {
		Point posA = human.getPosition();
		Point posB = mob.getPosition();
		DirectionEnum dir = human.getDirection();
		int i = 0;
		int j = 0;

		if (posA.getX() == posB.getX())
			i = Math.abs(posB.getY() - posA.getY());
		if (posA.getY() == posB.getY())
			j = Math.abs(posB.getX() - posA.getX());

		// beide Mobs m�ssen auf einer Linie stehen
		if (i == 0 && j == 0)
			return false;

		Point newPos = new Point(dir.getStep().getX() * j + posA.getX(), dir
				.getStep().getY() * i + posA.getY());
		if (!newPos.equals(posB))
			return false;

		return directLineOfSight(posA, posB);
	}

	private void performShootAction() {
		Mob currentMob = gd.getActiveTeam().getCurrentMob();
		if (!(currentMob instanceof Human))
			return;
		Human human = (Human) currentMob;
		if (human.getMunition() < 1 || human.getPoints() < 10)
			return;
		human.decrementPoints(10);
		human.decreaseMunition(1);
		shootField(human.getPosition(), human.getDirection().getStep(),
				human.getWeaponDamage());
	}

	private void shootField(Point position, Point step, int weaponDamage) {
		Point newPos = new Point(position.getX() + step.getX(), position.getY()
				+ step.getY());
		List<GameElement> field = gd.getStepManager().getElements(newPos);
		if (fieldIsAttackable(field) || fieldIsSolid(field)) {
			attackField(newPos, weaponDamage);
			return;
		}
		shootField(newPos, step, weaponDamage);
	}

	private void attackField(Point newPos, int damage) {
		showAttack(newPos);
		List<GameElement> field = gd.getStepManager().getElements(newPos);
		if (fieldIsAttackable(field) && field.size() > 1
				&& field.get(field.size() - 1) instanceof Mob) {
			Mob mob = (Mob) field.get(field.size() - 1);
			if (mob.decrimentHealth(damage))
				killMob(newPos);
			if (!gd.isLost() && !gd.isWon())
				updateBoard();
		}
	}

	private void showAttack(Point newPos) {
		if (!loadedFromTest) {
			gd.saveState();
			gd.setGameState(GameStateEnum.ATTACK);
			attackedField = newPos;
			updateBoard();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			gd.restoreState();
		}
	}

	public Point getAttackedField() {
		return attackedField;
	}

	private void updateBoard() {

		if (loadedFromTest)
			return;

		IUserInterfaceEvent<GameElement> ev = new DefaultUserInterfaceEvent<GameElement>();
		ev.setBoard(gd.getStepManager());

		LinkedHashMap<String, String[]> informationMap = new LinkedHashMap<String, String[]>();
		String selTeam = "Human";
		if (!gd.isTeam1IsActive())
			selTeam = "Monster";
		String[] levelInfos = { "" + gd.getLevel() };
		informationMap.put("level: ", levelInfos);
		String[] selTeamInfos = { selTeam };
		informationMap.put("Selected Team: ", selTeamInfos);
		String[] roundsInfo = { "" + gd.getPlayedRounds() + "/"
				+ gd.getMaxRounds() };
		informationMap.put("gespielte/maximale Rundenanzahl: ", roundsInfo);
		ev.setInformationMap(informationMap);

		try {
			ui.handleEvent(ev);
		} catch (SoundFailedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void rotateMob(boolean left) {
		// get the mobile object that is currently selected
		// TODO: what about "autonomous" monsters at the end of the turn? How
		// are they selected?
		Mob currentMob = gd.getActiveTeam().getCurrentMob();

		if (currentMob.getPoints() >= 1) {
			// turning costs 1 point
			currentMob.decrementPoints(1);

			if (left)
				currentMob.rotateLeft();
			else
				currentMob.rotateRight();
		}

	}

	public void performMobAction(boolean attack) {

		// get the mobile object that is currently selected
		// TODO: what about "autonomous" monsters at the end of the turn? How
		// are they selected?
		Mob currentMob = gd.getActiveTeam().getCurrentMob();

		Point directionPoint = currentMob.getDirection().getStep();
		Point movingToPoint = new Point(currentMob.getPosition().getX()
				+ directionPoint.getX(), currentMob.getPosition().getY()
				+ directionPoint.getY());
		List<GameElement> elementsOnDestination = gd.getStepManager()
				.getElements(movingToPoint);
		// check direction the mob wants to move to and if it is able to move
		// (so if the coordinates are valid basically)
		if (gd.getStepManager().checkCoordinates(movingToPoint)) {
			// if the field is not solid
			if (!fieldIsSolid(elementsOnDestination)) {
				if (currentMob.getPoints() >= 3) {
					if (elementsOnDestination.size() == 2) {

						if (currentMob instanceof Human) {
							Human h = (Human) currentMob;
							if (elementsOnDestination.get(1) instanceof Medikit) {
								Medikit m = (Medikit) elementsOnDestination
										.get(1);
								h.healMob(m.getHealth());
							}
							if (elementsOnDestination.get(1) instanceof Ammo) {
								Ammo a = (Ammo) elementsOnDestination.get(1);
								h.decreaseMunition(-a.getAmmo());
							}
							elementsOnDestination.remove(1);
						}
						// dont move mob/human after picking up ammo/medikit
						return;
					}

					// get elements of the current gamefield
					List<GameElement> elementsOnField = gd.getStepManager()
							.getElements(currentMob.getPosition());
					elementsOnField.remove(currentMob);

					gd.getActiveTeam().setCurrentMobPosition(movingToPoint);
					// set new position of MOB
					currentMob.setPosition(movingToPoint);
					// running costs 3 points
					currentMob.decrementPoints(3);
					// add MOB in new position in the gamefield
					gd.getStepManager().getElements(movingToPoint)
							.add(currentMob);
				}
			} else {
				List<Point> enemies = gd.getEnemyTeam().getMobPositions();
				// check if on neighbor-field there is an alien we want to
				// attack now
				if (enemies.contains(movingToPoint)
						&& currentMob.getPoints() >= 10 && attack) {
					// can we attack (need at least 10 points)
					// decrement action-points by 10
					currentMob.decrementPoints(10);
					// remove alien from all lists
					attackField(movingToPoint, currentMob.getMeeleDamage());
					// re-set the current selected mob, as the list has changed
					// the active mob itself didn't change its/his/her position
					// gd.setCurrentMobPosition(currentMob.getPosition(),
					// currentMob);
				} else if (currentMob instanceof Human) {
					GameElement elem = gd.getStepManager()
							.getElements(movingToPoint).get(0);
					if (elem instanceof Switch) {
						Switch sw = (Switch) elem;
						boolean locked = sw.changeState();
						Point door = sw.getDoor();
						List<GameElement> elems = gd.getStepManager()
								.getElements(door);
						if (elems.get(0) instanceof Door) {
							if (elems.size() > 1
									&& elems.get(elems.size() - 1) instanceof Mob) {
								killMob(door);
							}
							Door d = (Door) elems.get(0);
							d.setState(locked);
						}
					}
				}
			}
		}
	}

	private void killMob(Point movingToPoint) {
		Mob killedMob = gd.getMobOnPosition(movingToPoint);
		gd.deleteMob(killedMob);
		int removeIndex = gd.getStepManager().getElements(movingToPoint).size() - 1;
		gd.getStepManager().getElements(movingToPoint).remove(removeIndex);

		if (!loadedFromTest)
			ui.setToolTip(movingToPoint, null);
	}

	private boolean fieldIsSolid(List<GameElement> field) {
		for (GameElement ge : field) {
			if (ge.isSolid())
				return true;
		}
		return false;
	}

	private boolean fieldIsAttackable(List<GameElement> field) {
		for (GameElement ge : field) {
			if (ge instanceof Mob)
				return true;
		}
		return false;
	}

	public void endTurn() {

		if (!gd.isTeam1IsActive() || loadedFromTest)
			gd.setPlayedRounds(gd.getPlayedRounds() + 1);
		
		if(loadedFromTest && gd.isTeam1IsActive()) {
			gd.getActiveTeam().resetTeam(gd.getStepManager());
			return;
		}

		if ((gd.getGameState() == GameStateEnum.AIOnTurn
				&& !gd.getEnemyTeam().isDefeated() && !loadedFromTest)
				|| (!gd.getEnemyTeam().isDefeated() && !loadedFromTest)
				|| (loadedFromTest && !gd.isTeam1IsActive())) {
			gd.changeTeam();
			gd.getActiveTeam().resetTeam(gd.getStepManager());
			updateBoard();
		}

		if (gd.isLost()) {
			gd.setGameState(GameStateEnum.FINISHED);
			showLOSTWindow();
			return;
		}
		if (gd.isWon()) {
			gd.setGameState(GameStateEnum.FINISHED);
			showWonWindow();
			return;
		}

		if (gd.getActiveTeam().isUseAI()) {
			gd.setGameState(GameStateEnum.AIOnTurn);

			try {
				performAITurn(gd.getActiveTeam(), gd.getActiveTeam().getAi());
			} catch (InvalidLevelDataException e) {
				e.printStackTrace();
			}
		} else {
			gd.setGameState(GameStateEnum.RUNNING);
		}

		// updateBoard();
	}

	private void performAITurn(XcomTeam xcomTeam, XcomAIInterface xcomAi)
			throws InvalidLevelDataException {
		ActionEnum action;
		do {
			String move = xcomAi.getMove(levelManager.saveBoardAsLevel(gd));
			String[] temp = move.split(";");
			action = ActionEnum.getActionFromChar(temp[2].charAt(0));
			Mob currentMob = gd.getMobOnPosition(new Point(Integer
					.parseInt(temp[0]), Integer.parseInt(temp[1])));
			gd.getActiveTeam().setSelectedMob(currentMob);
			this.processEvent(new XcomActionEvent(action));
			if (action != ActionEnum.EndTurn && (gd.isLost() || gd.isWon())) {
				action = ActionEnum.EndTurn;
				this.processEvent(new XcomActionEvent(action));
			}
		} while (action != ActionEnum.EndTurn);
		/*
		 * move = xcomAi.getMove(levelManager.saveBoardAsLevel(gd)); temp =
		 * move.split(";"); currentMob = gd.getMobOnPosition(new
		 * Point(Integer.parseInt(temp[0]), Integer.parseInt(temp[1]))); action
		 * = ActionEnum.getActionFromChar(temp[2].charAt(0));
		 * gd.getActiveTeam().setSelectedMob(currentMob); this.processEvent(new
		 * XcomActionEvent(action)); if(action != ActionEnum.EndTurn &&
		 * (gd.isLost() || gd.isWon())){ action = ActionEnum.EndTurn;
		 * this.processEvent(new XcomActionEvent(action)); } }
		 */
	}

	public boolean showAttack() {
		return GameStateEnum.ATTACK == gd.getGameState();
	}

	private void resetGame() {
		gd.getTeam(true).resetTeam(gd.getStepManager());
		gd.getTeam(false).resetTeam(gd.getStepManager());
		gd.setGameState(GameStateEnum.RUNNING);
	}

	public XcomGameData getGd() {
		return gd;
	}

	public void setMapVisible(boolean b) {
		mapVisible = b;
	}

	public void changeMapVisibility() {
		mapVisible = !mapVisible;
		updateBoard();
	}

	public String getSavegame() {
		try {
			return levelManager.saveBoardAsLevel(gd);
		} catch (InvalidLevelDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "";
	}

	public boolean verifyLevel(String data) {
		Level<GameElement> lv = new Level<GameElement>(
				new XcomLevelInformationProvider(true));
		try {
			lv.loadLevel(data);
		} catch (InvalidLevelDataException ilde) {
			return false;
		}
		return true;
	}
}
